package net.gdface.sdk.thrift;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import net.gdface.image.ImageErrorException;
import net.gdface.sdk.CodeInfo;
import net.gdface.sdk.FRect;
import net.gdface.sdk.FaceApi;
import net.gdface.sdk.NotFaceDetectedException;
import net.gdface.thrift.TypeTransformer;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import net.gdface.thrift.ClientFactory;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.microsoft.thrifty.service.AsyncClientBase;
import com.microsoft.thrifty.service.ServiceMethodCallback;

import static com.google.common.base.Preconditions.*;
import static net.gdface.thrift.ThriftUtils.returnNull;
/**
 * 基于thrift/swift框架生成的client端代码提供{@link FaceApi}接口的RPC实现(线程安全)<br>
 * 转发所有{@link FaceApi}接口方法到{@link net.gdface.sdk.thrift.client.FaceApiClient}实例<br>
 * 所有服务端抛出的{@link RuntimeException}异常被封装到{@link ServiceRuntimeException}中抛出<br>
 * Example:<br>
 * <pre>
 *        FaceApiThriftClient thriftInstance = ClientFactory
 *            .builder()
 *            .setHostAndPort("127.0.0.1",26413)
 *            .build(FaceApiThriftClient.class);
 * </pre>
 * 计算机生成代码(generated by automated tools ThriftServiceDecoratorGenerator @author guyadong)<br>
 * @author guyadong
 *
 */
public class FaceApiThriftClient implements FaceApi {
    private final ClientFactory factory;

    public FaceApiThriftClient(ClientFactory factory) {
        super();
        this.factory = checkNotNull(factory,"factory is null");
    }
    /**
     * 服务异步调用接口
     *
     * @param <T>
     */
    private interface ServiceAsyncCall<T> {
        public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<T> callback);
    }
    /**
     * 异常调用转同步调用实现
     * @param transformer
     * @param serviceCall
     * @return
     * @throws Throwable
     */
    private <R,L>R syncCall(final Function<L, R> transformer,final ServiceAsyncCall<L> serviceCall) throws Throwable{
        final AtomicReference<R> res = new AtomicReference<R>(null);
        final AtomicReference<Throwable> err = new AtomicReference<Throwable>(null);
        /* 异步调用结束标志信号量 */
        final Object lock = new Object();
        final ServiceMethodCallback<L> callback = new ServiceMethodCallback<L>() {

            @Override
            public void onSuccess(L result) {
                res.set(transformer.apply(result));
                /* 异步调用结束,唤醒等待线程 */
                synchronized(lock){
                    lock.notifyAll();
                }
            }

            @Override
            public void onError(Throwable error) {
                err.set(error);
                /* 异步调用结束,唤醒等待线程 */
                synchronized(lock){
                    lock.notifyAll();
                }
            }
        };
        /* 连接关闭侦听器当检测到异常时调用callback来处理 */
        AsyncClientBase.Listener closeListener = new AsyncClientBase.Listener(){
                @Override
                public void onTransportClosed() {
                }
        
                @Override
                public void onError(Throwable error) {
                    callback.onError(error);
                }};
        net.gdface.sdk.thrift.client.FaceApiClient service = factory.applyInstance(net.gdface.sdk.thrift.client.FaceApiClient.class,closeListener);

        synchronized(lock){
            try {
                serviceCall.call(service,callback);
                /* 等待异步调用结束 */
                lock.wait();
            } catch (InterruptedException e) {
                err.set(e);
            } finally{
                try {
                    /* 调用结束关闭连接 */
                    service.close();
                } catch (IOException e) {}
            }
        }
        Throwable e = err.get(); 
        if(null != e){
            /** 判断异常类型返回null */
            returnNull(e);
        }
        return res.get();
    }    
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("FaceApiThriftClient [factory=");
        builder.append(factory);
        builder.append(",interface=");
        builder.append(FaceApi.class.getName());
        builder.append("]");
        return builder.toString();
    }
    @Override
    public double compare2Face(final byte[] imgData1,
        final CodeInfo facePos1,
        final byte[] imgData2,
        final CodeInfo facePos2) 
        throws ImageErrorException,NotFaceDetectedException{
        try{
            return syncCall(new Function<Double,Double>() {
                @Override
                public Double apply(Double input) {
                    return input;
                }},
                new ServiceAsyncCall<Double>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Double> nativeCallback){
                    service.compare2Face(TypeTransformer.getInstance().to(
                    imgData1,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    facePos1,
                    CodeInfo.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),TypeTransformer.getInstance().to(
                    imgData2,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    facePos2,
                    CodeInfo.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public double compareCode(final byte[] code1,
        final byte[] code2) 
        {
        try{
            return syncCall(new Function<Double,Double>() {
                @Override
                public Double apply(Double input) {
                    return input;
                }},
                new ServiceAsyncCall<Double>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Double> nativeCallback){
                    service.compareCode(TypeTransformer.getInstance().to(
                    code1,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    code2,
                    byte[].class,
                    okio.ByteString.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public double[] compareCodes(final byte[] code1,
        final CodeInfo[] codes) 
        {
        try{
            return syncCall(new Function<java.util.List<Double>,double[]>() {
                @Override
                public double[] apply(java.util.List<Double> input) {
                    return TypeTransformer.getInstance().todoubleArray(
                    input,
                    double.class,
                    double.class);
                }},
                new ServiceAsyncCall<java.util.List<Double>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<java.util.List<Double>> nativeCallback){
                    service.compareCodes(TypeTransformer.getInstance().to(
                    code1,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    codes,
                    CodeInfo.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public Map<CodeInfo, Double> compareFaces(final byte[] code,
        final byte[] imgData,
        final int faceNum) 
        throws ImageErrorException,NotFaceDetectedException{
        try{
            return syncCall(new Function<Map<net.gdface.sdk.thrift.client.CodeInfo,Double>,Map<CodeInfo, Double>>() {
                @Override
                public Map<CodeInfo, Double> apply(Map<net.gdface.sdk.thrift.client.CodeInfo,Double> input) {
                    return TypeTransformer.getInstance().to(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    Double.class,
                    CodeInfo.class,
                    Double.class);
                }},
                new ServiceAsyncCall<Map<net.gdface.sdk.thrift.client.CodeInfo,Double>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Map<net.gdface.sdk.thrift.client.CodeInfo,Double>> nativeCallback){
                    service.compareFaces(TypeTransformer.getInstance().to(
                    code,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),faceNum,nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public List<Double> compareFeatures(final byte[] code1,
        final List<byte[]> codes) 
        {
        try{
            return syncCall(new Function<List<Double>,List<Double>>() {
                @Override
                public List<Double> apply(List<Double> input) {
                    return TypeTransformer.getInstance().to(
                    input,
                    Double.class,
                    Double.class);
                }},
                new ServiceAsyncCall<List<Double>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<List<Double>> nativeCallback){
                    service.compareFeatures(TypeTransformer.getInstance().to(
                    code1,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    codes,
                    byte[].class,
                    okio.ByteString.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public double detectAndCompare2Face(final byte[] imgData1,
        final FRect detectRect1,
        final byte[] imgData2,
        final FRect detectRect2) 
        throws ImageErrorException,NotFaceDetectedException{
        try{
            return syncCall(new Function<Double,Double>() {
                @Override
                public Double apply(Double input) {
                    return input;
                }},
                new ServiceAsyncCall<Double>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Double> nativeCallback){
                    service.detectAndCompare2Face(TypeTransformer.getInstance().to(
                    imgData1,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    detectRect1,
                    FRect.class,
                    net.gdface.sdk.thrift.client.FRect.class),TypeTransformer.getInstance().to(
                    imgData2,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    detectRect2,
                    FRect.class,
                    net.gdface.sdk.thrift.client.FRect.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public CodeInfo[] detectAndGetCodeInfo(final byte[] imgData,
        final int faceNum) 
        throws ImageErrorException,NotFaceDetectedException{
        try{
            return syncCall(new Function<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>,CodeInfo[]>() {
                @Override
                public CodeInfo[] apply(java.util.List<net.gdface.sdk.thrift.client.CodeInfo> input) {
                    return TypeTransformer.getInstance().toArray(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    CodeInfo.class);
                }},
                new ServiceAsyncCall<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>> nativeCallback){
                    service.detectAndGetCodeInfo(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),faceNum,nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public CodeInfo detectCenterFace(final byte[] imgData) 
        throws ImageErrorException,NotFaceDetectedException{
        try{
            return syncCall(new Function<net.gdface.sdk.thrift.client.CodeInfo,CodeInfo>() {
                @Override
                public CodeInfo apply(net.gdface.sdk.thrift.client.CodeInfo input) {
                    return TypeTransformer.getInstance().to(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    CodeInfo.class);
                }},
                new ServiceAsyncCall<net.gdface.sdk.thrift.client.CodeInfo>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<net.gdface.sdk.thrift.client.CodeInfo> nativeCallback){
                    service.detectCenterFace(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public CodeInfo[] detectFace(final byte[] imgData) 
        throws ImageErrorException{
        try{
            return syncCall(new Function<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>,CodeInfo[]>() {
                @Override
                public CodeInfo[] apply(java.util.List<net.gdface.sdk.thrift.client.CodeInfo> input) {
                    return TypeTransformer.getInstance().toArray(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    CodeInfo.class);
                }},
                new ServiceAsyncCall<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>> nativeCallback){
                    service.detectFace(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public CodeInfo[] getCodeInfo(final byte[] imgData,
        final int faceNum,
        final CodeInfo[] facePos) 
        throws NotFaceDetectedException{
        try{
            return syncCall(new Function<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>,CodeInfo[]>() {
                @Override
                public CodeInfo[] apply(java.util.List<net.gdface.sdk.thrift.client.CodeInfo> input) {
                    return TypeTransformer.getInstance().toArray(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    CodeInfo.class);
                }},
                new ServiceAsyncCall<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<java.util.List<net.gdface.sdk.thrift.client.CodeInfo>> nativeCallback){
                    service.getCodeInfo(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),faceNum,TypeTransformer.getInstance().to(
                    facePos,
                    CodeInfo.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public CodeInfo getCodeInfo(final byte[] imgData,
        final CodeInfo facePos) 
        {
        try{
            return syncCall(new Function<net.gdface.sdk.thrift.client.CodeInfo,CodeInfo>() {
                @Override
                public CodeInfo apply(net.gdface.sdk.thrift.client.CodeInfo input) {
                    return TypeTransformer.getInstance().to(
                    input,
                    net.gdface.sdk.thrift.client.CodeInfo.class,
                    CodeInfo.class);
                }},
                new ServiceAsyncCall<net.gdface.sdk.thrift.client.CodeInfo>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<net.gdface.sdk.thrift.client.CodeInfo> nativeCallback){
                    service.getCodeInfoSingle(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),TypeTransformer.getInstance().to(
                    facePos,
                    CodeInfo.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public byte[] getFeature(final Map<ByteBuffer, CodeInfo> faces) 
        throws NotFaceDetectedException{
        try{
            return syncCall(new Function<okio.ByteString,byte[]>() {
                @Override
                public byte[] apply(okio.ByteString input) {
                    return TypeTransformer.getInstance().to(
                    input,
                    okio.ByteString.class,
                    byte[].class);
                }},
                new ServiceAsyncCall<okio.ByteString>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<okio.ByteString> nativeCallback){
                    service.getFeature(TypeTransformer.getInstance().to(
                    faces,
                    ByteBuffer.class,
                    CodeInfo.class,
                    okio.ByteString.class,
                    net.gdface.sdk.thrift.client.CodeInfo.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.NotFaceDetectedException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.NotFaceDetectedException.class,
                    NotFaceDetectedException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public boolean hasFace(final byte[] imgData) 
        throws ImageErrorException{
        try{
            return syncCall(new Function<Boolean,Boolean>() {
                @Override
                public Boolean apply(Boolean input) {
                    return input;
                }},
                new ServiceAsyncCall<Boolean>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Boolean> nativeCallback){
                    service.hasFace(TypeTransformer.getInstance().to(
                    imgData,
                    byte[].class,
                    okio.ByteString.class),nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ImageErrorException e){
            throw TypeTransformer.getInstance().to(
                    e,
                    net.gdface.sdk.thrift.client.ImageErrorException.class,
                    ImageErrorException.class);
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
    @Override
    public boolean isLocal(){
        return false;
    }
    @Override
    public boolean multiFaceFeature() 
        {
        try{
            return syncCall(new Function<Boolean,Boolean>() {
                @Override
                public Boolean apply(Boolean input) {
                    return input;
                }},
                new ServiceAsyncCall<Boolean>(){
                @Override
                public void call(net.gdface.sdk.thrift.client.FaceApiClient service,ServiceMethodCallback<Boolean> nativeCallback){
                    service.multiFaceFeature(nativeCallback);
                }});
        }
        catch(net.gdface.sdk.thrift.client.ServiceRuntimeException e){
            throw new ServiceRuntimeException(e);
        }
        catch (Throwable e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    }
}